
package flp.solvers.ant;

import flp.solvers.*;
import flp.Layout;
import flp.problem.*;
import flp.*;
import flp.InboundUsage;
import flp.exceptions.FlowImpossibleException;
import flp.exceptions.LayoutBuildException;
import java.util.Random;

/** 
 * Klasa szukajaca rozwiazania FLP przy uzyciu algorytmu mrowkowego.
 * 
 * @author Lukasz
 */

public class AntSolver extends Solver{
    
    static {
        preferableCostFunction = Layout.CostFunction.DAGSP;
    }
    
     /**Ustawienie dla algorytmu.*/
     private AntSettings settings;
     
     /** Tablica feromonow. */
     private float[][] pheromonesTable;
     
     /**Do wyboru sciezki.*/
     private static Random rnd=new Random();
     
     /**Ilosc mozliwych terminali.*/
     private int terminalsAmount;
     
     
     /**Ilosc stanowisk.*/
     private int buildingSitesCount;
     
     /**Zapamietuje najgorszy z rozwiazan.*/
     int worstCost=0;
     
     
     /**
      * Tablica pomocna przy wybieraniu na podstawie
      * prawdopodobienstwa kolejnego terminala.
      * Miejsca parzyste to IN.
      * Nieparzyste to OUT.
      * Przedostatni element tabeli to CL.
      * Ostatni element tabeli to brak wykorzystania.
      * i/2 mowi do ktorego duzego portu nalezy.
      * Ta sama zasada dotyczy tablicy feromonow.
      */
     int[][] probableTable;
     
     public AntSolver(ProblemInstance currentProblem, AntSettings settings,
                      IOptimizationMonitor optMon){
          super(currentProblem, optMon);
          
          this.settings=settings;
          
          terminalsAmount=currentProblem.getRiverFacilitiesCount()*2+2; 
                                    // dla kazdego duzego portu po IN i OU + CL + brak
          
          buildingSitesCount=currentProblem.getBuildingSitesCount();
          
          pheromonesTable=new float[terminalsAmount][buildingSitesCount];
          
          for(int i=0;i<terminalsAmount;i++) 
             for(int j=0;j<buildingSitesCount;j++) pheromonesTable[i][j]=0;
          
          
          probableTable=new int[terminalsAmount][buildingSitesCount];
          
          //na poczatku przedzialy sa takie same dla wszystkich terminali
          for(int i=0;i<terminalsAmount;i++)
              for(int j=0;j<buildingSitesCount;j++)
                   probableTable[i][j]=100+i*100;   //na poczatku dla kazdego terminala 100 pktow.
    
     }
    public AntSettings getSettings(){
        return settings;
    }
    
    /**
     * Metoda przeprowadzająca obliczenia
     * 
     * @return Najlepsze znalezione rozwiązanie.
     */
     public Layout computeSolution() throws LayoutBuildException, FlowImpossibleException
     {         
         /**Layout wybranych przez mrowke terminali.*/
         Layout localU=new Layout(new Usage[buildingSitesCount],toSolve);
         
         /**Layout najoptymalniej wybranych w tym przebiegu terminali.*/
         Layout localBest=null;
         
         /**Layout najoptymalniej wybranych terminali.*/
         Layout everBest=null;
         
         /**Layout najoptymalniej wybranych w tym poprzednim przebiegu terminali.*/
         Layout prevBest=null;
         
         
         /**Zlicza ilosc przebiegow algorytmu bez zmiany rozwiazania.*/
         int withNoChange=0;
         
         /**Flaga czy sie cos zmienilo.*/
         int changed=0;
         
         
         int licznik=0;
         
         int cost;
         
         
         Usage tablica[]=new Usage[buildingSitesCount];
         
         try{
             for(int j=0;j<settings.getAntNumber();j++){

                 localU=Layout.getInstance(toSolve);
                 if(localU.checkCorrectness()){

                            licznik++;
                            //if(worstCost<(cost=localU.getCost(preferableCostFunction)))worstCost=cost;

                            if(localBest==null || localBest.compareTo(localU, preferableCostFunction)==1){
                                localBest=localU.clone();

                            }
                   }

              }
             worstCost=localBest.getCost();

             //System.out.println(localBest);


             if(localBest!=null){
                   // System.out.println("z randa: "+localBest.getCost(preferableCostFunction));

                    pheromonesUpdate(localBest);
                    if(everBest==null) everBest=localBest.clone(); //edit by Pipen (musi byc klonowanie a nie przepisanie referencji!!
                    if(prevBest==null) prevBest=localBest.clone(); //edit by Pipen

                    if(localBest.contrastWith(prevBest)==true) changed=1; //jezeli layout jest taki sam
                    if(everBest.compareTo(localBest, preferableCostFunction)==1){
                        everBest=localBest.clone();  //tak samo jw
                        System.out.println(localBest);
                    }

                    prevBest=localBest.clone();             

                    if(changed==1) withNoChange++;
                    else           withNoChange=0;

                    changed=0;

             }

             localBest=null;

             for(int i=0;i<settings.getMaxIterations()-1 && withNoChange<settings.getIterationsWithNoChange();i++){

                 localBest=null;



                 for(int j=0;j<settings.getAntNumber();j++){
                     do{
                         goAntGo(localU);
                     } while(!localU.checkCorrectness());
                    if(localU.checkCorrectness()){

                            licznik++;

                            if(localBest==null || localBest.compareTo(localU, preferableCostFunction)==1){
                                localBest=localU.clone();
                                Object[] values = { i, withNoChange, localBest.getCost(preferableCostFunction), everBest.getCost(preferableCostFunction)};
                                optMonitor.markProgress(values);
                            }
                    }


                 }


                  /** @author Mateusz
                  *  Pobieranie danych dla monitora
                  *  { iteracja, iteracje bez zmian, localBestCost , everBestCost};
                  */
                 Object[] values = { i, withNoChange, localBest.getCost(preferableCostFunction), everBest.getCost(preferableCostFunction)};
                 optMonitor.markProgress(values);


                 if(localBest!=null){


                    if(everBest==null) everBest=localBest.clone();
                    if(prevBest==null) prevBest=localBest.clone();

                    if(localBest.contrastWith(prevBest)==true) changed=1; //jezeli layout jest taki sam
                    if(everBest.compareTo(localBest, preferableCostFunction)==1)
                    {
                        everBest=localBest.clone();
                    }


                    pheromonesUpdate(localBest);      
                    prevBest=localBest.clone();             

                    if(changed==1) withNoChange++;
                    else           withNoChange=0;

                    //System.out.println(localBest+" "+withNoChange);
                    //System.out.println();

                    changed=0;
                 }




                 localBest=null;
                 if (stoppedFlag){
                     break;
                 }             
             } 

         } catch(LayoutBuildException e){
             
                // metoda zwraca null, ktory handler interpretuje jako rzucenie
                // wyjatku "LayoutBuildException"
                return null;
         }
         return everBest;
     }
     
     /**
      * Metoda idzie mrowka po wszystkich stanowiskach.
      * 
      * @param Track Layout do wypelnienia.
      */
     private void goAntGo(Layout Track){
        Usage usg;
        
        /**Do testowania czy terminal legalny.*/
         Assignment asg;
         
         for(int i=0;i<buildingSitesCount;i++){
            do{ 
               usg=chooseYourDestiny(i);
              asg=new Assignment(i,usg);
            } while(!toSolve.getConstraints().isLegal(asg));
            Track.setPlace(i,usg);
        }
     }
    
     /**
      * Metoda losuje na i-tym stanowisku terminal.
      * @param i Nr stanowiska.
      */
     private Usage chooseYourDestiny(int i){
        
        int choose=rnd.nextInt(probableTable[terminalsAmount-1][i]);   //losujemy liczbe z przedzialu od 0 do max il.pktow
        
        int j=0;            //wyszukujemy gdzie w tabeli probableTable trafilismy
        
        while(choose>probableTable[j][i]) j++;
        
        if(j==terminalsAmount-1) return null;
        else if(j==terminalsAmount-2) return new CleaningUsage();
        else {
            if(j%2==0) return new InboundUsage(j/2);        //parzyste to IN
            return new OutboundUsage(j/2);                  //nieparzyste to OUT
        }
     }
     
     /**
      * Metoda aktualizuje tablice probableTable na podstawie feromonow.
      */
     private void updateProbableTable(){               //generuje tabele pomocna przy wyborze terminala
        for(int i=0;i<terminalsAmount;i++)
            for(int j=0;j<buildingSitesCount;j++)
         probableTable[i][j]=(int) (100 + 100 * pheromonesTable[i][j]);
         for(int i=1;i<terminalsAmount;i++)
             for(int j=0;j<buildingSitesCount;j++)
             probableTable[i][j]=(int)(probableTable[i-1][j] + probableTable[i][j]);
     }
     
     /**
      * Metoda aktualizuje feromony w tablicy pheromonesTable.
      * 
      * @param actual Layout na podstawie ktorego przebiega aktualizacja.
      */
     private void pheromonesUpdate(Layout actual) throws FlowImpossibleException {

         //parowanie feromonow
         for(int i=0;i<terminalsAmount;i++) 
             for(int j=0;j<buildingSitesCount;j++)
                  pheromonesTable[i][j]*=settings.getEvaporationRatio();
         
         int which;    //oznacza miejsce w tabeli danego terminala
         
         for(int i=0;i<buildingSitesCount;i++){
           
            if(actual.getPlace(i) instanceof InboundUsage)
                   which=((InboundUsage)actual.getPlace(i)).getSourceRiverFacilityID()*2;
            else if(actual.getPlace(i) instanceof OutboundUsage)
                   which=((OutboundUsage)actual.getPlace(i)).getTargetRiverFacilityID()*2+1;
            else if(actual.getPlace(i) instanceof CleaningUsage) 
                    which=terminalsAmount-2;
            else which=terminalsAmount-1;
            
            pheromonesTable[which][i]+=((float)worstCost)*5/(actual.getCost(preferableCostFunction));
         }
         
         updateProbableTable();     //i uaktualnij tabele punktow
     }
     
}


